﻿using CTM.Web.Data;
using CTM.Web.Models.Input;
using CTM.Web.Models.View.Conversation;
using CTM.Web.Models.View.Feedback;
using CTM.Web.Models.View.Home;
using CTM.Web.Models.View.Report;
using CTM.Web.Repositories;
using CTM.Web.Utils;
using System.Collections.Generic;
using System.Linq;

namespace CTM.Web.Services
{
    public interface IPostService : IApplicationService<Post>
    {
        IEnumerable<ReportItemViewModel> GetConversations(CTM.Web.Models.ApplicationUser manager, ReportSearchModel searchModel, bool isHR);
        IEnumerable<ReportItemViewModel> GetFeedbacks(CTM.Web.Models.ApplicationUser manager, ReportSearchModel searchModel, bool isHR);

        IEnumerable<ConversationViewModel> SearchConversations(ApplicationUserManager userManager, HomeConversationSearchModel searchModel);
        IEnumerable<FeedbackViewModel> SearchFeedbacks(ApplicationUserManager userManager, FeedbackSearchModel searchModel);
    }

    public class PostService : ApplicationService<Post>, IPostService
    {
        private IUnitOfWork unitOfWork;
        private IPostRepository postRepository;
        private IAccountService accountService;
        private IOrgService orgService;

        public PostService(IUnitOfWork unitOfWork, IPostRepository postRepository, IAccountService accountService, IOrgService orgService)
            : base(unitOfWork, postRepository)
        {
            this.unitOfWork = unitOfWork;
            this.postRepository = postRepository;
            this.accountService = accountService;
            this.orgService = orgService;
        }

        public IEnumerable<ConversationViewModel> SearchConversations(ApplicationUserManager userManager, HomeConversationSearchModel searchModel)
        {
            // Get all conversations
            var postQuery = this.Get(p => !p.IsDeleted && p.Category.Name == "Conversation");

            // Default search by user id & year
            if (GlobalVariables.SelectedUser == null)
            {
                postQuery = Enumerable.Empty<Models.Input.Post>();
                return postQuery.Select(p => new ConversationViewModel(p));
            }
            else
            {
                postQuery = postQuery.Where(p => p.Employee.Id == GlobalVariables.SelectedUser.Id);
            }

            if (searchModel != null)
            {
                if (searchModel.Year.HasValue)
                    postQuery = postQuery.Where(p => p.CreatedOn.Year == searchModel.Year);

                // Search by emoticon & type
                postQuery = postQuery.Where(p => searchModel.Statuses.Contains(p.Emoticon.Value));
                postQuery = postQuery.Where(p => searchModel.Types.Contains(p.Type.Value));

                // Search by keyword in title, description, tags & manager (case-insensitive)
                // The result is the combination of every record that matches
                if (!string.IsNullOrWhiteSpace(searchModel.Keyword))
                {
                    // Normalize the keyword
                    searchModel.Keyword = searchModel.Keyword.Trim();
                    searchModel.Keyword = searchModel.Keyword.ToUpper();

                    postQuery = postQuery.Where(n => n.Title.ToUpper().Contains(searchModel.Keyword)
                        || n.Description.ToUpper().Contains(searchModel.Keyword)
                        || n.Tags.Any(t => t.Name.ToUpper().Contains(searchModel.Keyword))
                        || n.Manager.DisplayName.ToUpper().Contains(searchModel.Keyword));
                }
            }

            bool isOwner = GlobalVariables.CurrentUser.Id == GlobalVariables.SelectedUser.Id;
            if (isOwner || ApplicationHelpers.IsManager(accountService, userManager, GlobalVariables.CurrentUser.Id, GlobalVariables.SelectedUser.Id))
            {
                // HR staffs & the owner can see all conversations
                // Keep all records
            }
            else
            {
                // The current user will see nothing
                postQuery = Enumerable.Empty<Models.Input.Post>();
            }

            return postQuery.Select(p => new ConversationViewModel(p));
        }

        public IEnumerable<FeedbackViewModel> SearchFeedbacks(ApplicationUserManager userManager, FeedbackSearchModel searchModel)
        {
            // Default search by user id
            if (GlobalVariables.SelectedUser == null)
            {
                return Enumerable.Empty<FeedbackViewModel>();
            }

            // Get all conversations
            var postQuery = this.Get(p => !p.IsDeleted && p.Category.Name == "Feedback");

            // Get feedback for selected user only
            if (searchModel.SelectedPageOnly)
            {
                postQuery = postQuery.Where(f => f.Employee.Id == GlobalVariables.SelectedUser.Id);
            }                

            if (searchModel != null)
            {
                // Search by side (given to me/given by me)
                if (!searchModel.GivenToMe && !searchModel.GivenByMe)
                {
                    return Enumerable.Empty<FeedbackViewModel>();
                }
                if (!searchModel.GivenToMe)
                {
                    postQuery = postQuery.Where(f => f.Employee.Id != GlobalVariables.SelectedUser.Id);
                }
                if (!searchModel.GivenByMe)
                {
                    postQuery = postQuery.Where(f => f.Manager.Id != GlobalVariables.SelectedUser.Id);
                }

                // Search by year
                if (searchModel.Year > 0)
                {
                    postQuery = postQuery.Where(f => f.CreatedOn.Year == searchModel.Year);
                }

                // Search by emoticon
                postQuery = postQuery.Where(f => searchModel.Emoticons.Contains(f.Emoticon.Value));

                // Search by keyword in description, tags & giver (case-insensitive)
                // The result is the combination of every record that matches
                if (!string.IsNullOrWhiteSpace(searchModel.Keyword))
                {
                    // Normalize the keyword
                    string keyword = searchModel.Keyword;
                    keyword = keyword.Trim();
                    keyword = keyword.ToUpper();

                    postQuery = postQuery.Where(f => f.Title.ToUpper().Contains(keyword)
                        || f.Description.ToUpper().Contains(keyword)
                        || f.Tags.Any(t => t.Name.ToUpper().Contains(keyword))
                        || f.Manager.DisplayName.ToUpper().Contains(keyword));
                }
            }

            bool isOwner = GlobalVariables.CurrentUser.Id == GlobalVariables.SelectedUser.Id;

            if (isOwner || ApplicationHelpers.IsManager(accountService, userManager, GlobalVariables.CurrentUser.Id, GlobalVariables.SelectedUser.Id))
            {
                // HR staffs & the owner can see all conversations
                // Keep all records
            }
            else
            {
                // The current user will see public feedbacks & private feedbacks created by him/her
                postQuery = postQuery.Where(f => !f.IsPrivate || f.Manager.Id == GlobalVariables.CurrentUser.Id);
            }

            return postQuery.Select(f => new FeedbackViewModel(f));
        }

        public IEnumerable<ReportItemViewModel> GetConversations(CTM.Web.Models.ApplicationUser manager, ReportSearchModel searchModel, bool isHR)
        {
            if (searchModel.Orgs == null || !searchModel.Orgs.Any())
                return new ReportItemViewModel[] { };

            IEnumerable<ReportItemViewModel> employees = null;

            if(isHR)
            {
                employees = accountService.Get(u=> searchModel.Orgs.Contains(u.Org.Id)).Select(u => new ReportItemViewModel
                {
                    Associate = u.DisplayName,
                    AssociateId = u.Id
                });
            }
            else if (!searchModel.IncludeIndirectEmployees || manager.ManageOrgs == null || !manager.ManageOrgs.Any())
            {
                employees = accountService.Get(u => u.Manager.Id == manager.Id && searchModel.Orgs.Contains(u.Org.Id)).Select(u => new ReportItemViewModel
                {
                    Associate = u.DisplayName,
                    AssociateId = u.Id
                });
            }
            else
            {
                employees = accountService.Get(u =>  searchModel.Orgs.Contains(u.Org.Id)).Select(u=>new ReportItemViewModel
                    {
                        Associate = u.DisplayName,
                        AssociateId = u.Id
                    });
            }

            if (!string.IsNullOrWhiteSpace(searchModel.Keyword))
            {
                // Normalize the keyword
                var keyword = searchModel.Keyword.ToUpper().Trim();
                employees = employees.Where(u => u.Associate.ToUpper().Contains(keyword)).ToList();
            }
            var employeeIds = employees.Select(e => e.AssociateId);

            var allPosts = this.Get(p => !p.IsDeleted && p.Category.Name == "Conversation"
                                                        && employeeIds.Contains(p.Employee.Id)
                                                        && searchModel.Year.Value == p.CreatedOn.Year);

            if(searchModel.Statuses.Any() && searchModel.Types.Any())
            {
                var posts = allPosts.Where(p => searchModel.Statuses.Contains(p.Emoticon.Value)
                                                        && searchModel.Types.Contains(p.Type.Value));
                
                if(!posts.Any())
                    return new ReportItemViewModel[] { };

                var summary = from p in posts 
                              group p by new { Id = p.Employee.Id, Name = p.Employee.DisplayName } into grouped
                              select new ReportItemViewModel
                              {
                                  AssociateId = grouped.Key.Id,
                                  Associate = grouped.Key.Name,
                                  NoFrownPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Frown) : 0,
                                  NoGoalsPost = grouped.Any() ? grouped.Count(p => p != null && p.Type.HasValue && p.Type == PostType.Goals) : 0,
                                  NoIndividualPost = grouped.Any() ? grouped.Count(p => p != null && p.Type.HasValue && p.Type == PostType.Individual) : 0,
                                  NoCareerPost = grouped.Any() ? grouped.Count(p => p != null && p.Type.HasValue && p.Type == PostType.Career) : 0,
                                  NoNeutralPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Neutral) : 0,
                                  NoSmilePost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Smile) : 0,
                                  NoMyPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id == manager.Id) : 0,
                                  NoOtherPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id != manager.Id) : 0,
                              };
                return summary.OrderBy(e => e.Associate).ToList();
            }

            var employeesWithPosts = allPosts.Select(p=>p.Employee.Id);
            var employeesWoPosts = employees.Where(e => !employeesWithPosts.Contains(e.AssociateId)).OrderBy(e => e.Associate).ToList();
            return employeesWoPosts;
        }

        public IEnumerable<ReportItemViewModel> GetFeedbacks(CTM.Web.Models.ApplicationUser manager, ReportSearchModel searchModel, bool isHR)
        {
            if (searchModel.Orgs == null || !searchModel.Orgs.Any())
                return new ReportItemViewModel[] { };
            
            IEnumerable<ReportItemViewModel> employees = null;

            if (isHR)
            {
                employees = accountService.Get(u => searchModel.Orgs.Contains(u.Org.Id)).Select(u => new ReportItemViewModel
                {
                    Associate = u.DisplayName,
                    AssociateId = u.Id
                });
            }
            else if(!searchModel.IncludeIndirectEmployees || manager.ManageOrgs == null || !manager.ManageOrgs.Any())
            {
                employees = accountService.Get(u => u.Manager.Id == manager.Id && searchModel.Orgs.Contains(u.Org.Id)).Select(u => new ReportItemViewModel
                {
                    Associate = u.DisplayName,
                    AssociateId = u.Id
                });
            }
            else
            {
                employees = accountService.Get(u => searchModel.Orgs.Contains(u.Org.Id)).Select(u => new ReportItemViewModel
                {
                    Associate = u.DisplayName,
                    AssociateId = u.Id
                });
            }

            if (!string.IsNullOrWhiteSpace(searchModel.Keyword))
            {
                // Normalize the keyword
                var keyword = searchModel.Keyword.ToUpper().Trim();
                employees = employees.Where(u => u.Associate.ToUpper().Contains(keyword)).ToList();
            }

            var employeeIds = employees.Select(e => e.AssociateId);
            var allPosts = this.Get(p => !p.IsDeleted && p.Category.Name == "Feedback"
                                                        && employeeIds.Contains(p.Employee.Id)
                                                        && searchModel.Year.Value == p.CreatedOn.Year);

            if (searchModel.Statuses.Any())
            {
                var posts = allPosts.Where(p => searchModel.Statuses.Contains(p.Emoticon.Value));

                if (!posts.Any())
                    return new ReportItemViewModel[] { };

                var summary = from p in posts 
                              group p by new { Id = p.Employee.Id, Name = p.Employee.DisplayName } into grouped
                              select new ReportItemViewModel
                              {
                                  AssociateId = grouped.Key.Id,
                                  Associate = grouped.Key.Name,
                                  NoFrownPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Frown) : 0,
                                  NoNeutralPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Neutral) : 0,
                                  NoSmilePost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Smile) : 0,
                                  NoMyPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id == manager.Id) : 0,
                                  NoOtherPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id != manager.Id) : 0,
                              };
                return summary.OrderBy(e => e.Associate).ToList();
            }

            var employeesWithPosts = allPosts.Select(p => p.Employee.Id);
            var employeesWoPosts = employees.Where(e => !employeesWithPosts.Contains(e.AssociateId)).OrderBy(e => e.Associate).ToList();
            return employeesWoPosts;

            //var summary = from e in employees
            //              join p in posts on e.AssociateId equals p.Employee.Id into p1
            //              from ep in p1.DefaultIfEmpty()
            //              group ep by new { Id = e.AssociateId, Name = e.Associate } into grouped
            //              select new ReportItemViewModel
            //              {
            //                  AssociateId = grouped.Key.Id,
            //                  Associate = grouped.Key.Name,
            //                  NoFrownPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Frown) : 0,
            //                  NoNeutralPost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Neutral) : 0,
            //                  NoSmilePost = grouped.Any() ? grouped.Count(p => p != null && p.Emoticon.HasValue && p.Emoticon == Emoticon.Smile) : 0,
            //                  NoMyPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id == manager.Id) : 0,
            //                  NoOtherPost = grouped.Any() ? grouped.Count(p => p != null && p.Manager != null && p.Manager.Id != manager.Id) : 0,
            //              };

        }
    }
}